package net.entelijan.cobean.bind.impl;

import java.beans.PropertyChangeEvent;
import java.util.Collection;

import net.entelijan.cobean.bind.IBeanProperty;
import net.entelijan.cobean.bind.IModelChangeListener;
import net.entelijan.cobean.bind.IPropertyBindingStrategy;
import net.entelijan.cobean.bind.IRemovableFactory;
import net.entelijan.cobean.core.IRemovable;
import net.entelijan.cobean.core.impl.INamedPropertyChangeListener;
import net.entelijan.cobean.core.impl.IPropertyChangeListenerAware;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

class BoundPropertyBindingModelChangeListener implements IModelChangeListener,
        INamedPropertyChangeListener, IRemovable {

    private static Log log = LogFactory.getLog(BoundPropertyBindingModelChangeListener.class);
    protected IBeanProperty modelBeanProperty;
    protected IBeanProperty boundBeanProperty;
    private IRemovableFactory removableFactory;
    private String name;
    private IPropertyBindingStrategy bindingStrategy;

    public BoundPropertyBindingModelChangeListener(IBeanProperty beanProperty, Object boundBean,
            String boundPropertyName, IPropertyBindingStrategy bindingStrategy,
            IRemovableFactory removableFactory) {
        this.name = boundPropertyName;
        this.modelBeanProperty = beanProperty;
        this.removableFactory = removableFactory;
        this.boundBeanProperty = bindingStrategy.createBeanProperty(boundBean, boundPropertyName);
        this.bindingStrategy = bindingStrategy;
        if (boundBean instanceof IPropertyChangeListenerAware) {
            ((IPropertyChangeListenerAware) boundBean).setPropertyChangeListener(this);
            log.debug("[<init>] Added property change listener=" + this + " to bean=" + boundBean);
        } else {
            // TODO add some code to enable the binding of classes that do not implement IPropertyChangeListenerAware. E.g. a dynamic proxy could be used.
            throw new IllegalArgumentException(
                    "Cannot bind '"
                            + boundBean
                            + "' with '"
                            + boundPropertyName
                            + "' because it does not implement a method for adding a property change listener.");
        }
        Object value = modelBeanProperty.getValue();
        log.debug("[<init>] setting '" + value + "' to '" + this.boundBeanProperty + "'");
        this.boundBeanProperty.setValue(value);
    }

    @Override
    public String getPropertyName() {
        return this.modelBeanProperty.getPropertyName();
    }

    @Override
    public void updateComponent() {
        Object newVal = this.modelBeanProperty.getValue();
        Object oldVal = this.boundBeanProperty.getValue();
        if (newVal != null && newVal instanceof Collection<?>) {
            log.debug("[updateComponent] wrap collection " + newVal);
            newVal = bindingStrategy.bind(newVal, new CobeanCollectionBinding(
                    this.modelBeanProperty), boundBeanProperty);
            modelBeanProperty.setValue(newVal);
        }
        log.debug("[updateComponent] newVal:'" + newVal + "' oldVal:'" + oldVal + "'");
        if (oldVal == null || !oldVal.equals(newVal)) {
            this.boundBeanProperty.setValue(newVal);
        }
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        if (evt != null && evt.getPropertyName().equals(this.name)) {
            Object newValue = evt.getNewValue();
            this.modelBeanProperty.setValue(newValue);
        }
    }

    @Override
    public void prepareComponent(Object oldVal, Object newVal) {
        // Nothing to be done here
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public String toString() {
        return "BoundPropertyBindingModelChangeListener [boundBean=" + boundBeanProperty
                + ", modelBeanProperty=" + modelBeanProperty + "]";
    }

    @Override
    public void remove() {
        log.debug("[remove] removableFactory='" + removableFactory + "'");
        if (this.removableFactory != null) {
            this.removableFactory.createRemovable().remove();
        }
    }

}
